import org.junit.{Before, Test}

import scala.collection.immutable
import scala.io.{BufferedSource, Source}

class Demo19StudentExam {
  // 定义全局变量
  var stu: List[Students] = _
  var sco: List[Score] = _
  var sub: List[Subject] = _


  var stuMap: Map[String, String] = _

  var subMap: Map[String, String] = _
  var subFullScoreMap: Map[String, Int] = _

  @Before
  // 加载学生、分数、科目数据
  def loadData(): Unit = {
    // 将学生数据中的每一条数据变成Students样例类对象 并构建一个List
    val stuBS: BufferedSource = Source
      .fromFile("data/students.txt")
    stu = stuBS
      .getLines()
      .toList
      .map(line => {
        val stuSplits: Array[String] = line.split(",")
        Students(stuSplits(0), stuSplits(1), stuSplits(2), stuSplits(3), stuSplits(4))
      })

    // 构建学生的Map 以id作为Key，name+clazz作为Value
    stuMap = stu.map(s => (s.id, s.name + "," + s.clazz)).toMap

    // 将分数数据中的每一条数据变成Score样例类对象 并构建一个List
    val scoBS: BufferedSource = Source
      .fromFile("data/score.txt")

    sco = scoBS
      .getLines()
      .toList
      .map(line => {
        val scoSplits: Array[String] = line.split(",")
        Score(scoSplits(0), scoSplits(1), scoSplits(2).toInt)
      })

    val subBS: BufferedSource = Source
      .fromFile("data/subject.txt")

    sub = subBS
      .getLines()
      .toList
      .map(line => {
        val subSplits: Array[String] = line.split(",")
        Subject(subSplits(0), subSplits(1), subSplits(2).toInt)
      })

    // 构建科目的Map 以subject_id作为Key，subject_name作为Value
    subMap = sub.map(s => (s.subject_id, s.subject_name)).toMap

    // 构建科目的Map（没有科目名，但有科目的总分）
    subFullScoreMap = sub.map(s => (s.subject_id, s.subject_score)).toMap

    // 关闭BS
    stuBS.close()
    scoBS.close()
    subBS.close()

  }

  @Test
  // 打印所有的数据
  def printAll(): Unit = {
    stu.take(10).foreach(println)
    sco.take(10).foreach(println)
    sub.take(10).foreach(println)
  }

  def filterWithIdsAndPrint(ids: List[String]): Unit = {
    // 遍历分数数据 再通过Map进行关联
    sco
      // 根据ids进行过滤
      .filter(s => ids.contains(s.id))
      .map(s => {
        val id: String = s.id
        val subject_id: String = s.subject_id
        // 通过学生id 在 stuMap取对应的name、clazz
        val nameAndClazz: String = stuMap.getOrElse(id, "")
        // 通过科目id 在 subMap取对应的科目名称
        val subject_name: String = subMap.getOrElse(subject_id, "")
        s"$id,$nameAndClazz,$subject_name,${s.score}"
      }).foreach(println)
  }

  @Test
  // 统计班级人数 [班级,人数]
  def clazzCnt(): Unit = {
    stu
      .map(stu => stu.clazz)
      .groupBy(clazz => clazz)
      .map(kv => s"${kv._1},${kv._2.size}")
      .foreach(println)
  }

  @Test
  // 统计学生的总分 [学号,学生姓名,学生年龄,总分]
  def stuSumScore(): Unit = {
    // 计算总分
    val sumScoreMap: Map[String, Int] = sco
      .groupBy(_.id)
      .map {
        case (name: String, scoList: List[Score]) =>
          val sumScore: Int = scoList.map(_.score).sum
          (name, sumScore)
      }

    // 遍历学生的信息数据 然后通过 id 去sumScoreMap获取学生总分
    stu
      .map(s => {
        val sumScore: Int = sumScoreMap.getOrElse(s.id, 0)
        s"${s.id},${s.name},${s.age},$sumScore"
      }).foreach(println)

  }


  @Test
  // 1、统计年级排名前十学生各科的分数 [学号,姓名，班级，科目，分数]
  def question01(): Unit = {
    // 计算每个学生的总分
    val top10Ids: List[String] = sco
      .groupBy(_.id)
      // 如果groupBy之后的map方法返回值类型是二元组 那么最终会得到一个Map
      .map(kv => {
        (kv._1, kv._2.map(_.score).sum)
      })
      .toList // 先转换成List 才能使用sortBy方法
      // 按照总分降序
      .sortBy(t2 => -t2._2)
      // 取前10的学生id
      .take(10)
      .map(_._1)

    //    top10Ids.foreach(println)

    filterWithIdsAndPrint(top10Ids)

  }

  @Test
  //2、统计总分大于年级平均分的学生 [学号，姓名，班级，总分]
  def question02(): Unit = {
    // 平均分 --> 计算每个学生的总分 --> 过滤出总分大于年级平均分的学生 --> 关联学生信息数据 获取 姓名，班级
    // 计算年级平均分
    val avgSumScore: Double = sco
      .map(_.score)
      .sum / stu.size.toDouble

    println(avgSumScore) // 372.704

    // 计算每个学生的总分
    sco
      .groupBy(_.id)
      .map(kv => (kv._1, kv._2.map(_.score).sum))
      // 过滤出总分大于年级平均分的学生
      .filter(kv => kv._2 > avgSumScore)
      .toList
      .sortBy(_._2) // （可选） 按照总分的升序排列
      .map(kv => {
        val nameAndClazz: String = stuMap.getOrElse(kv._1, "")
        s"${kv._1},$nameAndClazz,${kv._2}"
      })
      .foreach(println)


  }

  @Test
  // 3、统计每科都及格的学生 [学号，姓名，班级，科目，分数]
  def question03(): Unit = {
    // 思路：将分数表与科目表关联 --> 获取每门科目的总分 --> 计算科目有没有及格，将没有及格的成绩过滤 --> 统计最终每个人有多少科目是及格的 --> 找到所有科目都及格的学生id --> 后续步骤同问题1
    val everySubPassIds: List[String] = sco
      .map(s => {
        val subFullScore: Int = subFullScoreMap.getOrElse(s.subject_id, 0)
        (s.id, s.score, subFullScore)
      })
      .filter(t3 => t3._2.toDouble / t3._3 >= 0.6)
      .groupBy(_._1)
      .map(kv => (kv._1, kv._2.size))
      .filter(_._2 == 6)
      .keys
      .toList

    filterWithIdsAndPrint(everySubPassIds)

  }

  @Test
  // 4、统计每个班级的前三名 [学号，姓名，班级，分数] TopN
  def question04(): Unit = {
    sco
      .groupBy(_.id)
      // 计算总分
      .map(kv => (kv._1, kv._2.map(_.score).sum))
      // 关联学生数据 获取 班级、姓名
      .map(kv => {
        val nameAndClazz: String = stuMap.getOrElse(kv._1, "")
        val arr: Array[String] = nameAndClazz.split(",")
        val name: String = arr(0)
        val clazz: String = arr(1)
        (kv._1, name, clazz, kv._2)
      })
      // 按照班级分组
      .groupBy(_._3)
      // 依次对每个班级进行处理
      .flatMap(kv => {
        // 按照总分降序 取出前三名
        kv._2.toList.sortBy(-_._4).take(3)
      })
      .map(t4 => s"${t4._1},${t4._2},${t4._3},${t4._4}")
      .foreach(println)
  }

  @Test
  // 5、统计偏科最严重的前100名学生  [学号，姓名，班级，科目，分数]
  def question05(): Unit = {
    // 思路：将每门科目的成绩转换成百分制（归一化处理） --> 计算每个人的平均成绩 --> 计算方差 --> 取出前100名学生的id --> 后续步骤同问题1
    val varianceTop100Ids: List[String] = sco
      .map(s => {
        val subFullScore: Int = subFullScoreMap.getOrElse(s.subject_id, 0)
        (s.id, s.score.toDouble / subFullScore * 100)
      })
      .groupBy(_._1)
      .map(kv => {
        // 计算平均分
        val avgScore: Double = kv._2.map(_._2).sum / kv._2.size
        // 计算方差
        val variance: Double = kv._2.map(t2 => {
          Math.pow(t2._2 - avgScore, 2)
        }).sum / kv._2.size
        (kv._1, variance)
      })
      .toList
      .sortBy(-_._2)
      .take(100)
      .map(_._1)


    //    varianceTop100Ids.foreach(println)

    filterWithIdsAndPrint(varianceTop100Ids)

  }
}

case class Students(id: String, name: String, age: String, gender: String, clazz: String)

case class Score(id: String, subject_id: String, score: Int)

case class Subject(subject_id: String, subject_name: String, subject_score: Int)